#include <QDebug>
#include <QJSEngine>
#include "macrorule.h"

QJSEngine js;

MacroRule::MacroRule(const Expression& pattern, const Expression& replacement,
                     const QString& macroReplacement, MacroOperation op,
                     QList<MacroVariable> vars, Translator &translator)
  : Rule(pattern, replacement),
    _macroReplacement(macroReplacement),
    _operation(op),
    _variables(vars),
    _translator(translator)
  {
  }

MacroRule::MacroRule(const Rule &rule, const QString& macroReplacement, MacroOperation op,
          QList<MacroVariable> vars, Translator &translator)
  : Rule(rule),
    _macroReplacement(macroReplacement),
    _operation(op),
    _variables(vars),
    _translator(translator)
  {
  }

MacroRule& MacroRule::operator=(const MacroRule &rhs)
  {
  this->_pattern = rhs._pattern;
  this->_replacement = rhs._replacement;
  this->_macroReplacement = rhs._macroReplacement;
  this->_operation = rhs._operation;
  this->_variables = rhs._variables;
  this->_translator = rhs._translator; // is this ok?
  return *this;
  }


// generates the rules from macro, based on all labels from given expression.
// WARNING! as getLabelDictionary, this function requires that exp is the same as _pattern (excluding labels)
QList<Rule> MacroRule::generateRules(const Expression &exp)
  {
  // First: get dictionary so that we can get to know all %1, %2, and so on
  QMap<QString, QStringList> dict = getLabelDictionary(exp);
  QMap<QString, QStringList>::const_iterator it = dict.constBegin();
  QMap<QString, int> substitutions;
  // read sizes of %1, %2 and so on
  while (it != dict.constEnd())
    {
    if (!it.key().contains("."))
      substitutions.insert(it.key() + ".size", it.value().size());
    ++it;
    }

  // Knowing sizes of %1, %2 and so on, we can deduce int values from QStrings in MacroVariables
  QList< QPair<int, int> > vars;
  QList<int> initialValues;
  for (int i = 0; i < _variables.size(); i++)
    {
    QString sMax = _variables.at(i).maxValue;
    QString sMin = _variables.at(i).minValue;
    QMap<QString, int>::const_iterator mit = substitutions.constBegin();
    while (mit != substitutions.constEnd())
      {
      sMax = sMax.replace(mit.key(), QString::number(mit.value()));
      sMin = sMin.replace(mit.key(), QString::number(mit.value()));
      ++mit;
      }
    int max = js.evaluate(sMax).toInt();
    int min = js.evaluate(sMin).toInt();
    vars.append(QPair<int, int>(min, max));    
    initialValues.append(min);
    }

  // Generate all possible combinations(?)
  // e.g. if we have only one var %i, and its min=0 and max=2, allPossibleCombinations look like this:
  // {0}, {1}, {2}
  // allPossibleValues.size() is not a number of vars - it's a number of all combinations. Each combination consists of const number of vars.
  QList< QList<int> > allPossibleValues;
  allPossibleValues.append(initialValues);
  QList<int> tmp;
  while (1)
    {
    tmp = getNextCombination(vars, allPossibleValues.last());
    if (tmp == initialValues)
      break;
    allPossibleValues.append(tmp);
    }

  QList<Rule> rules;
  if (allPossibleValues.size() == 0)
    return rules;

  // now for the meat: Basing off allPossibleValues, we generate a Rule for each combination.
  for (int i = 0; i < allPossibleValues.size(); i++) // for each combination
    {    
    // _macroReplacement => sth like "delta(%1.%i, %2.%i)"
    QString sMacroReplacement = _macroReplacement;
    for (int j = 0; j < allPossibleValues[i].size(); j++) // each var
      {
      sMacroReplacement = sMacroReplacement.replace(_variables[j].name,
                                                    QString::number(allPossibleValues[i][j])); // in combination i, read var no. j
      }
    Expression replacement;
    // qDebug() << sMacroReplacement;
    QString sReplacement = _translator.expToString(_replacement); // TODO: this is a waste. We should have this as QString.
    sReplacement.replace("macro", sMacroReplacement);
    _translator.stringToExp(sReplacement, replacement);

    Rule rule(_pattern, replacement);

    //  <!--
    //  Wartość zmiennej wyznacza rozmiar etykiet słowa w którym ta zmienna jest użyta.

    //  Więc dla każdej etykiety w _macroReplacement trzeba znaleźć jakie tam są zmienne
    //  następnie dla każdej zmiennej trzeba sprawdzić z jakiego zbioru etykiet jest wywoływana

    //  Np jeśli zmienna %i będzie wołana z %1 (%1.%i) to oznacza że trzeba dodać warunek:
    //  %1.size = aktualna wartość %i.
    //  -->
    for (int k = 0; k < _variables.size(); k++)
      {
      int index = 2; // the earliest we will find is on index 3:  %1.%i
      do
        {
        index = _macroReplacement.indexOf(_variables[k].name, index+1);
        if (index == -1)
          break;

        QString labelId = "";
        int i = index - 2;
        while (_macroReplacement[i] != '%')
          {
          labelId = _macroReplacement[i] + labelId;
          i--;
          if (i < 0)
            break;
          }
        if (i < 0)
          continue;

        // %labelId.size has to be equal to allPossibleValues[k].size() // this is a shot based on the old code which I don't get.
        Condition condition;
        condition.arguments = QStringList({ "%" + labelId + ".size",
                                            QString::number(allPossibleValues[k].size()) });
        condition.type = Condition::CT_EQUALS;
        rule.addCondition(condition);
        } while (index != -1);
      }
    rule.addConditions(this->_conditions);
    rules.append(rule);
    }
  return rules;
  return QList<Rule>();
  }

QList<int> MacroRule::getNextCombination(QList< QPair<int, int> > vars, QList<int> prevValues)
  {
  if (vars.size() != prevValues.size())
    {
    qCritical() << Q_FUNC_INFO << "invalid sizes of arguments.";
    return prevValues;
    }

  QList<int> newValues = prevValues;
  newValues[newValues.size() - 1]++;  // let's increase the last var.

  for (int i = vars.size() - 1; i >= 0; i--)
    {
    if (newValues.at(i) > vars.at(i).second)  // if we made this variable too big
      {
      if (i > 0)
        {
        newValues[i] = vars.at(i).first;  // we reset it to minimal possible value
        newValues[i-1]++;                 // and increase previous var.
        }
      else
        {
        // first element is maximum - we made a full loop, we need to reset all vars to minimal values.
        for (int j = 0; j < vars.size(); j++)
          newValues[j] = vars.at(j).first;
        }
      }
    }

  return newValues;
  }
